<?php
/**
 * @link https://www.kancloud.cn/cleverstone/ymb2
 * @copyright Copyright (c) 2020 Yii Manager Software LLC
 */

namespace common\traits;

use Yii;
use builder\helper\Math;
use common\helper\App;
use common\helper\ResultObject;
use extend\Extend;
use extend\weixin\request\JsapiTradeCreate;
use extend\weixin\WxConf;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Crypto\Rsa;
use WeChatPay\Formatter;
use yii\helpers\Json;
use yii\web\Response;

/**
 * 微信
 */
trait WechatTrait
{
    /**
     * 微信JSApi支付下单
     * @param string $wxOpenID 微信开放ID
     * @param string $outOrderNo 商家订单号
     * @param float $amount 支付金额（元）
     * @param string $desc 支付业务简介
     * @param string $businessType 业务类型
     * @param int $businessID 业务ID
     * @return ResultObject
     * @throws \Exception
     */
    protected function wechat_JSApiPay($wxOpenID, $outOrderNo, $amount, $desc, $businessType, $businessID)
    {
        if (APP_RUN_SERVER_LOCAL) {
            return ResultObject::create([
                'error' => '当前应用运行在本地开发服务器，无法发起微信支付下单~'
            ]);
        }

        //向微信服务端发起支付申请
        $wxClient = Extend::wxpay()->setRequest(new JsapiTradeCreate([
            'description' => $desc, //支付描述，如：商品付款
            'out_trade_no' => $outOrderNo, //商家支付单号
            'amount' => [
                'total' => intval(Math::mul($amount, 100)), //总金额，单位分
            ],
            'payer' => [
                'openid' => $wxOpenID, //买家微信开放ID
            ],
        ], '订单付款', '/wechat/jsapi-notify'));//todo: 需要完成 /wechat/jsapi-notify 接口逻辑
        if (!$wxClient->execute($businessType, $businessID)) {
            throw new \Exception($wxClient->error);
        }

        //生成参数和参数签名
        $params = [
            'appId' => WxConf::APPID,
            'timeStamp' => (string)Formatter::timestamp(),
            'nonceStr' => Formatter::nonce(),
            'package' => "prepay_id={$wxClient->result['prepay_id']}",
        ];
        $params += [
            'paySign' => Rsa::sign(Formatter::joinedByLineFeed(...array_values($params)), WxConf::MERCHANT_PRIVATE_KEY),
            'signType' => 'RSA'
        ];

        return ResultObject::create([
            'result' => $params,
        ]);
    }

    /**
     * 微信支付消息通知验签解码
     * @param string $messageCategory 消息种类
     * @param string $businessType 业务类型
     * @param string $businessID 业务ID
     * @return ResultObject
     */
    protected function wechat_messageDecode($messageCategory, $businessType, $businessID)
    {
        $bodyParams = file_get_contents('php://input');
        $header = Yii::$app->request->headers;

        $resultObject = ResultObject::create();
        $wechatPaySignature = $header->get('Wechatpay-Signature');
        $wechatPayTimestamp = $header->get('Wechatpay-Timestamp', 0);
        $wechatPayNonce = $header->get('Wechatpay-Nonce');
        $apiV3Key = WxConf::APIv3_KEY;

        //检查通知时间偏移量，允许5分钟之内的偏移
        $isOffsetValid = 300 >= abs(Formatter::timestamp() - $wechatPayTimestamp);
        if (!$isOffsetValid) {
            $resultObject->error = '出错了！头部参数未定义或通知时间和签名中通知时间偏移量过大。';
            App::logError($messageCategory, [
                'Request' => $wechatPayTimestamp,
                'Error' => $resultObject->error
            ], $businessType, $businessID);
            return $resultObject;
        }

        //检查签名
        if (!Rsa::verify(Formatter::joinedByLineFeed($wechatPayTimestamp, $wechatPayNonce, $bodyParams), $wechatPaySignature, WxConf::PLATFORM_CERTIFICATE)) {
            $resultObject->error = '出错了！数据的签名校验出错。';
            App::logError($messageCategory, [
                'Request' => compact('wechatPayTimestamp', 'wechatPayNonce', 'bodyParams', 'wechatPaySignature'),
                'Error' => $resultObject->error
            ], $businessType, $businessID);
            return $resultObject;
        }

        $bodyParams = Json::decode($bodyParams);
        /** @since php7.1.0 */
        [ 'resource' => ['ciphertext' => $ciphertext, 'nonce' => $nonce, 'associated_data' => $aad] ] = $bodyParams;
        $resultObject->result = Json::decode(AesGcm::decrypt($ciphertext, $apiV3Key, $nonce, $aad));
        $resultObject->extra = $bodyParams;
        App::logDebug($messageCategory, $resultObject->result, $businessType, $businessID);

        return $resultObject;
    }

    /**
     * 微信支付通知应答：接收失败。
     * @param string $errorMessage 错误消息
     * @return Response
     */
    protected function wechat_asFail($errorMessage)
    {
        $response = Yii::$app->response;
        $response->format = Response::FORMAT_JSON;
        $response->data = ['code' => 'FAIL', 'message' => $errorMessage];

        return $response->setStatusCode(500);
    }
}